// Copyright (c) 2007-2008, 
// Trevor McCauley, http://www.senocular.com, 
// Alessandro Crugnola, http://www.sephiroth.it,
// Marc Speck, http://www.faindu.com
// All Rights Reserved. The following is Source Code and is subject to all restrictions 
// on such code as contained in the license accompanying this product.
package com.faindu.editImage
{
import com.faindu.editImage.base.BaseControl;
import com.faindu.editImage.base.ControlsPositions;
import com.faindu.editImage.base.EditImageEvent;
import com.faindu.editImage.base.IBaseTool;
import com.faindu.editImage.base.IEditImageTools;
import com.faindu.editImage.base.ImageLoader;
import com.faindu.editImage.crop.CropTool;
import com.faindu.editImage.crop.ICropTool;
import com.faindu.editImage.flip.FlipTool;
import com.faindu.editImage.flip.IFlipTool;
import com.faindu.editImage.manipulations.IImageManipulations;
import com.faindu.editImage.manipulations.ImageManipulations;
import com.faindu.editImage.move.IMoveTool;
import com.faindu.editImage.move.MoveTool;
import com.faindu.editImage.outline.OutlineTool;
import com.faindu.editImage.rotation.IRotateTool;
import com.faindu.editImage.rotation.RotateTool;
import com.faindu.editImage.scale.ScaleTool;

import flash.display.Bitmap;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;

import mx.logging.ILogger;

/**
 * Dispatched when the user presses any control of a tool.
 *
 * @eventType com.faindu.editImage.base.EditImageEvent.CONTROL_DOWN
 */
[Event(name="controlDown", type="com.faindu.editImage.base.EditImageEvent")]

/**
 * Dispatched when the user has pressed a control and moves while still pressing 
 * the mouse. 
 *
 * @eventType com.faindu.editImage.base.EditImageEvent.CONTROL_MOVE
 */
[Event(name="controlMove", type="com.faindu.editImage.base.EditImageEvent")]

/**
 * EditImage loads one image and allows the user to manipulate it. There are the
 * following tools available:
 * 
 * <p>
 * <ul>
 * <li><b>Crop Tool</b>: Cut out parts of the image.</li>
 * <li><b>Scale Tool</b>: Resize the image.</li>
 * <li><b>Rotate Tool</b>: Rotate the image around its center.</li>
 * <li><b>Move Tool</b>: Move the image to an other position.</li>
 * </ul>
 * </p> 
 * 
 * <p> While cropping corners, scaling corners or rotating the image, hold down the shiftkey 
 * to remove the constrains of the transformation.
 * </p>
 */	 
public class EditImage extends ImageLoader implements IEditImage, IEditImageTools
{
	/**
	 * Constructor.
	 */
	public function EditImage()
	{
		super()
		addEventListener( EVENT_IMAGE_ADDED , imageAddedHandler )
	}
	private var logger: ILogger
	
	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------		
	/**
	 * <code>updateControlsEnabled</code> adds all tools like 
	 * <code>ScaleTool</code> to <code>toolSprites</code>.
	 * 
	 * @see #updateControlsEnabled()
	 * @see com.faindu.editImage.base.BaseTool.updateControlContainer()
	 */
	private var toolSprites:Sprite = new Sprite();
	
	//--------------------------------------------------------------------------
	//
	//  Getter properties
	//
	//--------------------------------------------------------------------------	
	//----------------------------------
	//  controlsPositions
	//----------------------------------	
	private var _controlsPositions: ControlsPositions = new ControlsPositions()
	/**
	 * Stores the possible positions for controls. All points are in the inner coordinate
	 * system.
	 */
	public function get controlsPositions(): ControlsPositions
	{ 
		return _controlsPositions 
	}

	//----------------------------------
	//  initialized
	//----------------------------------	
	/** @private */
	protected var _toolsInitialized: Boolean
	/**
	 * @inheritDoc
	 */
	public function get toolsInitialized(): Boolean
	{
		return _toolsInitialized
	}
	
	//----------------------------------
	//  transform tools
	//----------------------------------
	// all tools are lazily created and loaded because in quite a few cases, only the image
	// is shown witout any tools.
	/** @private */
	protected var _moveTool: IMoveTool
	/**
	 * Tool for moving the image around.
	 */
	public function get moveTool(): IMoveTool
	{
		if ( ! _toolsInitialized )
			initTools()
		return _moveTool
	}

	/** @private */
	protected var _scaleTool: IBaseTool
	/**
	 * Tool for changing the displayed size of the image.
	 */	
	public function get scaleTool(): IBaseTool
	{
		if ( ! _toolsInitialized )
			initTools()
		return _scaleTool
	}

	/** @private */
	protected var _rotateTool: IRotateTool
	/**
	 * Tool for rotating the image.
	 */	
	public function get rotateTool(): IRotateTool
	{
		if ( ! _toolsInitialized )
			initTools()
		return _rotateTool
	}
	
	/** @private */
	protected var _cropTool: ICropTool
	/**
	 * Tool for rotating the image.
	 */	
	public function get cropTool(): ICropTool
	{
		if ( ! _toolsInitialized )
			initTools()
		return _cropTool
	}

	/** @private */
	protected var _outlineTool: IBaseTool
	/**
	 * @private
	 */	
	public function get outlineTool(): IBaseTool
	{
		if ( ! _toolsInitialized )
			initTools()
		return _outlineTool
	}
	
	/** @private */
	protected var _flipTool: IFlipTool
	/**
	 * @private
	 */	
	public function get flipTool(): IFlipTool
	{
		if ( ! _toolsInitialized )
			initTools()
		return _flipTool
	}
	
	//--------------------------------------------------------------------------
	//
	//  Getter/Setter properties
	//
	//--------------------------------------------------------------------------		
	//----------------------------------
	//  movingAreaWidth
	//----------------------------------
	private var _movingAreaWidth: Number = 500;
	/**
	 * @copy com.faindu.editImage.move.IMoveTool#movingAreaWidth
	 * 
	 * @default 500
	 */
	[Bindable]
	public function get movingAreaWidth(): Number
	{
		return _movingAreaWidth
	}
	public function set movingAreaWidth( value:Number ): void
	{
		if ( _movingAreaWidth != value )
		{
			_movingAreaWidth = value
			if ( moveTool )
			{
				moveTool.movingAreaWidth = _movingAreaWidth
			}
		}
	}

	//----------------------------------
	//  movingAreaHeight
	//----------------------------------	
	private var _movingAreaHeight: Number = 500;
	/**
	 * @copy com.faindu.editImage.move.IMoveTool#movingAreaWidth
	 * 
	 * @default 500
	 */
	[Bindable]
	public function get movingAreaHeight(): Number
	{
		return _movingAreaHeight
	}
	public function set movingAreaHeight( value:Number ): void
	{
		if ( _movingAreaHeight != value )
		{
			_movingAreaHeight = value
			if ( moveTool )
			{
				moveTool.movingAreaHeight = _movingAreaHeight
			}
		}
	}

	//----------------------------------
	//  snapToTop
	//----------------------------------
	/**
	 * If <code>snapToTop</code> or <code>snapToLeft</code> is true,
	 * the most outer point is stored in <code>snapPoint</code>.
	 */
	private var snapPoint: Point
	
	/** @private */	 
	protected var _snapToTop: Boolean = true
	/**
	 * @inheritDoc
	 */
	public function get snapToTop(): Boolean
	{
		return _snapToTop
	}
	public function set snapToTop( value:Boolean ): void
	{
		_snapToTop = value
	}

	//----------------------------------
	//  snapToLeft
	//----------------------------------
	/** @private */	
	protected var _snapToLeft: Boolean = true
	/**
	 * @inheritDoc
	 */
	public function get snapToLeft(): Boolean
	{
		return _snapToLeft
	}
	public function set snapToLeft( value:Boolean ): void
	{
		_snapToLeft = value
	}

	//----------------------------------
	//  controlSize
	//----------------------------------		
	private var _controlSize:uint = 20;
	/**
	 * @inheritDoc
	 * 
	 * @default 20
	 * @see com.faindu.editImage.base.BaseControl
	 */
	public function get controlSize(): uint 
	{
		return _controlSize;
	}
	public function set controlSize( value:uint ): void 
	{
		if ( _controlSize != value ) 
		{
			_controlSize = value
		}
	}

	//----------------------------------
	//  currentControl
	//----------------------------------		
	private var _currentControl: BaseControl
	/**
	 * The current control being used if being manipulated.
	 * This value is null if the user is not transforming the image.
	 */
	public function get currentControl():BaseControl
	{
		return _currentControl
	}
	
	//----------------------------------
	//  toolMatrix
	//----------------------------------
	private var _toolMatrix:Matrix = new Matrix()
	/**
	 * The transform matrix of the tool as it exists in its own coordinate space.
	 * This is not a clone, so be carefull with referencing the matrix.
	 */
	public function get toolMatrix(): Matrix 
	{
		return _toolMatrix
	}
	public function set toolMatrix( value:Matrix ): void 
	{
		_toolMatrix = value 
	}

    //==========================================================================
    //
    //  Init
    //
    //--------------------------------------------------------------------------  
	/**
	 * Initialize the tools such as scale or crop and the manipulations property.
	 * The getters of the tools or <code>manipulations</code> property call <code>init</code>.
	 * This means that by default, none of the tools and the manipulations is instaniated.
	 */
	protected function initTools():void
	{
		_toolsInitialized = true
		
		_moveTool = new MoveTool()
		_scaleTool = new ScaleTool()
		_cropTool = new CropTool()
		_rotateTool = new RotateTool()
		_outlineTool = new OutlineTool()
		_flipTool = new FlipTool()
		_imageManipulations = new ImageManipulations( this as EditImage )
		
		_moveTool.init(		this, startInteractionHandler )
		_scaleTool.init( 	this, startInteractionHandler )
		_cropTool.init( 	this, startInteractionHandler )
		_rotateTool.init( 	this, startInteractionHandler )
		_outlineTool.init( 	this, startInteractionHandler )
		_flipTool.init( 	this, startInteractionHandler )

		if ( _image )
		{
			updateMatrixWithImageMatrix()
			drawToolControls()		 
		}
	}

	/**
	 * Update the controls in the <code>toolSprites</code> and add <code>toolSprites</code> 
	 * to the display list. When the <code>image</code> has been loaded and 
	 * <code>imageHolder</code> has been added to the child list, <code>imageAddedHandler</code> 
	 * is called.   
	 * 
	 * @see #image
	 * @see #imageHolder
	 */
	private function imageAddedHandler( event:Event ): void
	{
		addChild( toolSprites )
		
		// check whether the tools have been initialized
		if ( _moveTool )
		{
			updateMatrixWithImageMatrix()
			drawToolControls()		
		}
	}
	
	/**
	 * Draws all controls in all tools.
	 */
	public function drawToolControls(): void
	{
		cropTool.draw()
		scaleTool.draw()
		rotateTool.draw()
		// moveTool.draw() moveTool does not have any controls
		outlineTool.draw()
	}

    //==========================================================================
    //
    //  Size EditImage
    //
    //--------------------------------------------------------------------------  
	/**
	 * Set the <code>width</code> and <code>height</code> of EditImage to the 
	 * most outer points of the image.
	 */
	override protected function measure():void
	{
		super.measure()
        measuredMinWidth = 10
		measuredMinHeight = 10
		
		if ( toolsInitialized )
		{
			var topLeft: Point = controlsPositions.topLeftSnapPoint
			var bottomRight: Point = controlsPositions.bottomRightSnapPoint
			measuredWidth = bottomRight.x - topLeft.x
            measuredHeight = bottomRight.y - topLeft.y
		}
		else if ( _image )
		{
			measuredWidth = _image.width/2
            measuredHeight = _image.height/2
		}
	}

	//==========================================================================
    //
    //  Enable/Disable tool and make them visible/invisble
    //
    //--------------------------------------------------------------------------  	
	/**
	 * @inheritDoc
	 * 
	 * @see #updateToolSprites()
	 */
	public function updateToolsEnabled():void
	{
		var aToolIsEnabled: Boolean
		
		// highest arrangement
		aToolIsEnabled = updateToolSprites( moveTool ) //use getter  of moveTool in case the tools are not initialized yet.
		aToolIsEnabled = updateToolSprites( _rotateTool ) 	|| aToolIsEnabled
		aToolIsEnabled = updateToolSprites( _cropTool ) 	|| aToolIsEnabled
		aToolIsEnabled = updateToolSprites( _scaleTool ) 	|| aToolIsEnabled
	
		_outlineTool.enabled = aToolIsEnabled
		updateToolSprites( _outlineTool )
	}

	/**
	 * Depending on <code>BaseTool.enabled</code>, add or remove 
	 * tools like <code>ScaleTool</code> from the display list.
	 *  
	 * @param tool Add or remove this tool.
	 * @return true if the tool is enabled.
	 * 
	 * @see com.faindu.editImage.base.BaseTool.enabled
	 */
	private function updateToolSprites( tool:IBaseTool ): Boolean 
	{
		var isChild:Boolean = toolSprites.contains( tool as Sprite )
		if ( tool.enabled ) 
		{
			// add child or sent to bottom if enabled
			if ( isChild ) 
			{
				toolSprites.setChildIndex( tool as Sprite, 0)
			}
			else
			{
				toolSprites.addChildAt( tool as Sprite, 0)
			}
			return true
		}
		else if ( isChild ) 
		{
			// removed if disabled
			toolSprites.removeChild( tool as Sprite )
		}
		return false
	}
	
	/**
	 * Shows or hides all tools including their controls that are enabled.
	 * 
	 * @param visible If true, show all controls. Otherwise hide all controls.
	 * 
	 * @see com.faindu.editImage.base.BaseTool.enabled
	 */
	protected function toolsAllVisible( visible:Boolean=true ): void
	{
		moveTool.visible 	= visible
		scaleTool.visible 	= visible
		rotateTool.visible	= visible
		cropTool.visible 	= visible
		outlineTool.visible = visible
	}
	
	//==========================================================================
    //
    //  interaction handlers
    //
    //--------------------------------------------------------------------------  
	/**
	 * When the user clicks and holds (<code>MOUSE_DOWN</code>) a control , 
	 * <code>startInteractionHandler</code> is called.
	 */
	protected function startInteractionHandler( mouseEvent:MouseEvent ): void 
	{
		_currentControl = mouseEvent.currentTarget as BaseControl
		if ( _currentControl ) 
		{
			// hide all visible tool controls and show them again after the interaction
			toolsAllVisible( false )
			
			// setup interaction
			updateMatrixWithImageMatrix()
			
			// if snapping is enabled, cache the topLeftSnapPoint
			if ( snapToTop || snapToLeft )
				snapPoint = controlsPositions.topLeftSnapPoint
			
			// usually startControlInteraction() of all tools are listening to this event. 
			dispatchEvent( new EditImageEvent( 	EditImageEvent.CONTROL_DOWN,
												mouseEvent.shiftKey,
												new Point( mouseX, mouseY ) ) )
			
			// setup stage events to manage control interaction
			stage.addEventListener( MouseEvent.MOUSE_MOVE, interactionHandler )
			stage.addEventListener( MouseEvent.MOUSE_UP,   endInteractionHandler, false )
			stage.addEventListener( MouseEvent.MOUSE_UP,   endInteractionHandler, true )
		}
	}
	
	/**
	 * When the user clicks and hold on a control and then moves the mouse , 
	 * <code>interactionHandler</code> is called.
	 */
	protected function interactionHandler( mouseEvent:MouseEvent ): void 
	{
		dispatchEvent( new EditImageEvent( 	EditImageEvent.CONTROL_MOVE, 
											mouseEvent.shiftKey,
											new Point( mouseX, mouseY ) ) )
		updateControlsPositions()
		_imageHolder.transform.matrix = _toolMatrix.clone()
		mouseEvent.updateAfterEvent()
	}

	/**
	 * When the user clicks and hold (<code>MOUSE_DOWN</code>) a control and then 
	 * releases the mouse button, <code>endInteractionHandler</code> is called.
	 */	
	protected function endInteractionHandler( event:MouseEvent ): void 
	{
		updateControlsPositions()
		
		if ( snapPoint && ( snapToTop || snapToLeft ) )
		{
			var moveDifference: Point = snapPoint.subtract( controlsPositions.topLeftSnapPoint )
			if ( snapToLeft )
				_toolMatrix.tx += moveDifference.x
			if ( snapToTop ) 
				_toolMatrix.ty += moveDifference.y
			
			updateControlsPositions()
			_imageHolder.transform.matrix = _toolMatrix.clone()
		}
		
		drawToolControls()
		rotateTool.deltaAngle = 0
		
		toolsAllVisible()
		
		stage.removeEventListener(MouseEvent.MOUSE_MOVE, interactionHandler)
		stage.removeEventListener(MouseEvent.MOUSE_UP, endInteractionHandler, false)
		stage.removeEventListener(MouseEvent.MOUSE_UP, endInteractionHandler, true)
						
		_currentControl = null
		invalidateSize()
	}
				
    //==========================================================================
    //
    //  Updates
    //
    //--------------------------------------------------------------------------  		
	/**
	 * Updates the matrix of imageHolder. In particular:
	 * <p>
	 * <ul>
	 * <li>Updates <code>toolMatrix</code> with the current matrix of imageHolder.</li>
	 * <li>Updates the possible positions of the controls.</li>
	 * <li>Assigns tollMatrix to the matrix of imageHolder.</li>
	 * </ul>
	 * </p> 
	 */
	protected function updateMatrixWithImageMatrix():void 
	{
		_toolMatrix = _imageHolder.transform.concatenatedMatrix.clone()
		// counter transform of the parents of the tool
		var current: Matrix = transform.concatenatedMatrix
		current.invert()
		_toolMatrix.concat( current )

		updateControlsPositions()
		
		_imageHolder.transform.matrix = _toolMatrix.clone()
	}
	
	/**
	 * Recalculate the possible positions the of the controls. 
	 * <code>updateControlsPositions</code> does not update 
	 * the displayed positions of the controls.
	 *  
	 * @param reset The toolMatrix and the cutout shall be reset.
	 * @param cutout The new cutout after reset.
	 */
	public function updateControlsPositions():void 
	{
		if ( _image )
		{
			var imageBounds: Rectangle
			
			var imageHolderScrollRect: Rectangle = _imageHolder.scrollRect
			if ( imageHolderScrollRect )
			{
				imageBounds = imageHolderScrollRect.clone()
				imageBounds.x = 0
				imageBounds.y = 0
			}
			else
			{
				// if imageHolder.scrollRect is reset to null with imageEdits
				imageBounds = new Rectangle( 0, 0,	image.bitmapData.width, 
													image.bitmapData.height )
			}
			
			_controlsPositions.update( _toolMatrix, imageBounds )
		}
	}
	
    //==========================================================================
    //
    //  Image Manipulations
    //
    //--------------------------------------------------------------------------  		
	/** @private */
	protected var _imageManipulations: IImageManipulations
	/**
	 * @inheritDoc
	 */	
	public function get imageManipulations(): IImageManipulations
	{
		if ( ! _toolsInitialized )	
			initTools()
		return _imageManipulations
	}	

    //==========================================================================
    //
    //  Image Manipulations
    //
    //--------------------------------------------------------------------------
	/**
	 * @inheritDoc
	 */	
	public function clone(): IEditImage
	{
		var clonedEditImage: IEditImage = new EditImage()
		var clonedBitmap: Bitmap = new Bitmap( _image.bitmapData ) // Should we call bitmapData.clone() ?
		clonedEditImage.showBitmap( clonedBitmap, _url )
		
		return clonedEditImage
	}    	
}
}
